-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add managed decimal support (as in the rust framework) #477
Add managed decimal support (as in the rust framework) #477
Conversation
@@ -10,18 +10,20 @@ export class Type { | |||
|
|||
private readonly name: string; | |||
private readonly typeParameters: Type[]; | |||
protected readonly cardinality: TypeCardinality; | |||
protected readonly metadata: any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe move the protected member last so that they are grouped by access modifiers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, as discussed, the type can be string
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -64,10 +66,18 @@ export class Type { | |||
return this.typeParameters; | |||
} | |||
|
|||
getMetadata(): any { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return type should be string
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -1,18 +1,21 @@ | |||
export class TypeFormula { | |||
name: string; | |||
metadata: any; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe change type to string
here, as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here we will let any because is the general object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you think it would be better to use object
type instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it can be also a primitive that's why I think any is a better option here
const typeParameters = hasTypeParameters | ||
? `<${this.typeParameters.map((tp) => tp.toString()).join(", ")}>` | ||
: ""; | ||
const baseName = `${this.name}${typeParameters}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did you intentionally left out the <>
? It used to be: return
${this.name}<${typeParameters}>;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<> is already added when we build the variable typeParameters <${this.typeParameters.map((tp) => tp.toString()).join(", ")}>
const payload = buffer.slice(0, length); | ||
|
||
const result = this.decodeTopLevel(payload, type); | ||
const decodedLength = length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
length
can simply be used instead of creating a new variable, but not a big deal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
} | ||
return new ManagedDecimalValue(native[0], native[1]); | ||
} | ||
errorContext.throwError(`(function: toEnumValue) unsupported native type ${typeof native}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be (function: toManagedDecimal)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -184,6 +185,118 @@ describe("test smart contract interactor", function () { | |||
assert.isTrue(typedBundle.returnCode.equals(ReturnCode.Ok)); | |||
}); | |||
|
|||
it("should interact with 'basic-features' (local testnet) using the SmartContractTransactionsFactory", async function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not using SmartContractTransactionsFactory
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
||
decodeTopLevel(buffer: Buffer, type: ManagedDecimalType): ManagedDecimalValue { | ||
if (buffer.length === 0) { | ||
return new ManagedDecimalValue(new BigNumber(0), 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic number 2
can be a constant - is it default scale of managed decimal
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
const isUsize = type.getMetadata() === "usize"; | ||
|
||
if (isUsize) { | ||
const u32Size = 4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a constant here:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
||
encodeNested(value: ManagedDecimalValue): Buffer { | ||
let buffers: Buffer[] = []; | ||
if (value.getType().getMetadata() == "usize") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's worth to add a function on ManagedDecimalValue
, such as isVariable()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can also say (optional):
const valueBuffer = uffer.from(this.binaryCodec.encodeTopLevel(new BigUIntValue(value.valueOf());
if (!value.isVariable) {
return valueBuffer;
}
const scaleBuffer = Buffer.from(this.binaryCodec.encodeNested(new U32Value(value.getScale()));
return Buffer.concat([...]);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not working becauze if is not variable the value is encodet top level if is variable the value is encodedNested
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, I add it on type and use it here
|
||
const isUsize = type.getMetadata() === "usize"; | ||
|
||
if (isUsize) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth to add maybe a function on ManagedDecimalType
, such as type.isVariable()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
isGenericType(): boolean { | ||
return this.typeParameters.length > 0; | ||
} | ||
|
||
hasMetadata(): boolean { | ||
return this.metadata !== null && this.metadata !== undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, !!
operator 🙈 (the "bang, bang, you're a boolean operator").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
return false; | ||
} | ||
|
||
return this.value == other.value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe also compare scale?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't need to compare the scale in order to compare the values, I added some tests to test it
return false; | ||
} | ||
|
||
return this.value == other.value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work when comparing instances of BigNumber
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it works
/** | ||
* Returns whether two objects have the same value. | ||
*/ | ||
equals(other: ManagedDecimalSignedValue): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same questions as above.
} | ||
|
||
toString(): string { | ||
return this.value.toFixed(this.scale); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's have some small unit tests for ManagedDecimalValue
and ManagedDecimalSignedValue
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
this.learnedTypesMap.delete(type.getName()); | ||
this.learnedTypesMap.set(type.getName(), type); | ||
if (type.getName() === "ManagedDecimal" || type.getName() === "ManagedDecimalSigned") { | ||
this.learnedTypesMap.delete(type.getName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed separately, fix is needed here.
} | ||
|
||
private getFullNameForGeneric(): string { | ||
const hasTypeParameters = this.getTypeParameters.length > 0; | ||
const hasTypeParameters = this.getTypeParameters().length > 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.isGenericType()
could have been used, but this does the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The equals()
function should be fixed. All the others are optional comments and ideas.
src/abi/typeFormulaParser.ts
Outdated
@@ -81,9 +81,10 @@ export class TypeFormulaParser { | |||
private acquireTypeWithParameters(stack: any[]): TypeFormula { | |||
const typeParameters = this.acquireTypeParameters(stack); | |||
const typeName = stack.pop(); | |||
const metadata = typeParameters[0].name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be more explicit that we deal with metadata only for some types, variable should be defined under the scope of if managed decimal
(I know it works the same).
Optional, can also stay as it is.
} | ||
|
||
encodeNested(value: ManagedDecimalValue): Buffer { | ||
let buffers: Buffer[] = []; | ||
if (value.getType().getMetadata() == "usize") { | ||
const managedDecimalType = value.getType() as ManagedDecimalType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we add isVariable()
not only to the type, but also to the value, we work around without type assertions.
return this.metadata; | ||
} | ||
|
||
isVariable(): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Useful both on type and value.
constructor(value: BigNumber.Value, scale: number) { | ||
super(new ManagedDecimalType(scale)); | ||
constructor(value: BigNumber.Value, scale: number, isVariableDecimals: boolean = false) { | ||
super(new ManagedDecimalSignedType(isVariableDecimals ? "usize" : scale)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also in ManagedDecimalValue
- is it worth to encapsulate this tricky logic somewhere? E.g. named constructor of the types? 🙈
@@ -84,8 +89,8 @@ export class ManagedDecimalSignedValue extends TypedValue { | |||
private readonly value: BigNumber; | |||
private readonly scale: number; | |||
|
|||
constructor(value: BigNumber.Value, scale: number) { | |||
super(new ManagedDecimalType(scale)); | |||
constructor(value: BigNumber.Value, scale: number, isVariableDecimals: boolean = false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameter name isVariableDecimals
is not consistent with parameter name of ManagedDecimalValue
constructor. Maybe isVariable
for both of them?
@@ -26,6 +26,6 @@ export class TypeExpressionParser { | |||
|
|||
private typeFormulaToType(typeFormula: TypeFormula): Type { | |||
const typeParameters = typeFormula.typeParameters.map((typeFormula) => this.typeFormulaToType(typeFormula)); | |||
return new Type(typeFormula.name, typeParameters, undefined, typeFormula.metadata); | |||
return new Type(typeFormula.name, typeParameters, TypeCardinality.fixed(1), typeFormula.metadata); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wrong before. Maybe undefined
is better here (if all tests still pass). Cardinality == 1 is tricky (it's a white lie), since here we also create & return variadic types.
Can be either undefined
of fixed(1)
- since the latter is the default anyway. When applying the Python-like simplifications (in the not very close future), this will get simplified (no cardinality abstraction etc.).
let firstValue = new ManagedDecimalValue(new BigNumber(1), 2, false); | ||
let secondValue = new ManagedDecimalValue(new BigNumber(2), 2, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here / above / below - can also be const
.
}); | ||
|
||
it("should set the correct scale when variable decimals", () => { | ||
let value = new ManagedDecimalValue(new BigNumber(1.3), 2, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for not thinking of this before - how about:
new ManagedDecimalValue("42.43", 8);
new ManagedDecimalValue("42.43", "usize");
Using the trick type ManagedDecimalScale = number | "usize"
.
let firstValue = new ManagedDecimalValue(new BigNumber(1.234), 3, false); | ||
let secondValue = new ManagedDecimalValue(new BigNumber(12.34), 2, false); | ||
|
||
assert.isFalse(firstValue.equals(secondValue)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's have a "positive" equality test / equal values, as well.
}); | ||
|
||
it("should compare correctly two managed decimals even with different scale", () => { | ||
let firstValue = new ManagedDecimalValue(new BigNumber(1.234), 3, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For documentation purposes, when a BigNumber
is created from non-integers, let's pass them as a string. Maybe in our examples, drop the BigNumber
wrapping e.g.
new ManagedDecimalValue("1.234", 3, false);
Instead of:
new ManagedDecimalValue(new BigNumber(...), 3, false);
Since in the constructor of ManagedDecimalValue
this is handled correctly anyway.
275bb49
Added support for
ManagedDecimal
andManagedDecimalSigned
.See: